#include "Defs.h"
#include "Filter.h"
#include "Config.h"
#include "Context.h"
#include "Utils.h"
#include "Log.h"


extern Config_T * g_configPtr;
extern HINSTANCE g_hInstance;


BOOL OnGetFilterVersion
(
	HTTP_FILTER_VERSION * pVer
)
{
	// 
	// get module pathname
	//

	char szModulePath[MAX_PATH];
	DWORD dwModulePath = GetModuleFileName(g_hInstance, szModulePath, sizeof(szModulePath));
	if( (dwModulePath == 0) || (dwModulePath == sizeof(szModulePath)) )
	{
		DWORD dwErr = GetLastError();
		ODS("Unable to call GetModuleFileName (%d)", dwErr);
		SetLastError(dwErr);
		return FALSE;
	}

	//
	// get module path name without extension
	//

	char szModulePathNoExt[MAX_PATH];
	FillMemory(szModulePathNoExt, sizeof(szModulePathNoExt), 0);
	RtlMoveMemory(szModulePathNoExt, szModulePath, String_T(szModulePath).LastIndexOf("."));
	

	//
	// get default trace file
	//

	char szTraceFile[MAX_PATH];
	FillMemory(szTraceFile, sizeof(szTraceFile), 0);

	_snprintf(szTraceFile, sizeof(szTraceFile) - 1, "%s.%d.log", 
		szModulePathNoExt,
		GetCurrentProcessId());

	try
	{
		Trace_T::Open(szTraceFile);
	}
	catch( std::bad_alloc & )
	{
		ODS("Unable to open trace file %s with error %d", szTraceFile, ERROR_NOT_ENOUGH_MEMORY);
		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return FALSE;
	}
	catch( Exception_T & exp )
	{
		ODS("Unable to open trace file %s with error %d", szTraceFile, exp.Error);
		SetLastError(exp.Error);
		return FALSE;
	}

	//
	// get default config file
	//

	char szConfigFile[MAX_PATH];
	FillMemory(szConfigFile, sizeof(szConfigFile), 0);
	RtlMoveMemory(szConfigFile, szModulePath, dwModulePath + 1);
	RtlMoveMemory(szConfigFile + dwModulePath - 3, "ini", 3);

	//
	// get module directory
	//

	char szModuleDir[MAX_PATH];
	FillMemory(szModuleDir, sizeof(szModuleDir), 0);
	RtlMoveMemory(szModuleDir, szModulePath, String_T(szModulePath).LastIndexOf("\\") + 1);
	lstrcat(szModuleDir, LOG_DIRECTORY);

	//
	// read configuration
	//
	
	try
	{
		g_configPtr = new Config_T(szModuleDir, szConfigFile);
	}
	catch( std::bad_alloc & )
	{
		Trace_T::Write(ERROR_NOT_ENOUGH_MEMORY, __FILE__, __LINE__, __FUNCSIG__);
		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return FALSE;
	}
	catch( Exception_T & exp )
	{
		Trace_T::Write(exp.Message);
		SetLastError(exp.Error);
		return FALSE;
	}

	//
	// Perform the normal ISAPI Filter startup
	//
	
	pVer->dwFilterVersion = HTTP_FILTER_REVISION;

	if( HIWORD(pVer->dwServerFilterVersion) == 6 )
		pVer->dwFlags = SF_NOTIFY_PREPROC_HEADERS;
	else
		pVer->dwFlags = SF_NOTIFY_READ_RAW_DATA;

	pVer->dwFlags |= SF_NOTIFY_SEND_RESPONSE		|
					 SF_NOTIFY_END_OF_NET_SESSION	|

					 SF_NOTIFY_SECURE_PORT			|
					 SF_NOTIFY_NONSECURE_PORT;

	if( g_configPtr->isHighPriorityRule.flag )
		pVer->dwFlags |= SF_NOTIFY_ORDER_HIGH;
	else
		pVer->dwFlags |= SF_NOTIFY_ORDER_LOW;

	return TRUE;
}

Error_T::Code_T OnReadRawData
(
	PHTTP_FILTER_CONTEXT pFC,
	PHTTP_FILTER_RAW_DATA pRaw,
	Config_T * configPtr
)
{
	Error_T::Code_T code = Error_T::ERR_WINDOWS;
	Context_T * contextPtr = 0;

	// 
	// Get filter context
	//
	
	if( pFC->pFilterContext == NULL )
	{
		pFC->pFilterContext = new Context_T(pFC, configPtr->rulesPtr);
		contextPtr = (Context_T *)pFC->pFilterContext;
	}
	else
	{
		contextPtr = (Context_T *)pFC->pFilterContext;
		if( contextPtr->isNewRequest )
		{
			contextPtr->Reset(configPtr->rulesPtr);
		}
	}
	
	//
	// If we're still parsing the headers, then
	// concat the incoming stream
	//

	if( !contextPtr->isEntity )
	{
		contextPtr->rawStream.Concat(Buffer_T((unsigned char *)pRaw->pvInData, pRaw->cbInData));

		//
		// Check if the http headers were all sent
		//

		unsigned int ix = contextPtr->rawStream.IndexOf("\r\n\r\n");
		if( ix != contextPtr->rawStream.Length() )
		{
			//
			// Adjust the headers and entity buffers
			//

			contextPtr->isEntity = true;
			contextPtr->rawEntity = contextPtr->rawStream.SubBuffer(ix + 4);
			contextPtr->entityLength = contextPtr->rawEntity.Length();

			//
			// Parse http headers
			//

			code = contextPtr->request.Parse(contextPtr->rawStream.SubBuffer(0, ix + 4));
			if( Error_T::ERR_NONE != code )
			{
				return code;
			}

			//
			// Go over each zone and check with which zone
			// the current request matches
			//

			unsigned int zoneIndex = 0;

			for( ; zoneIndex < configPtr->zoneList.Count() ; zoneIndex++ )
			{
				Zone_T & zone = *configPtr->zoneList[zoneIndex];

				if( zone.hasTargetAddr && !String_T(zone.szTargetAddr).Equals(contextPtr->session.localAddr, false) )
				{
					continue;
				}

				if( zone.hasTargetPort && !String_T(zone.szTargetPort).Equals(contextPtr->session.serverPort, false) )
				{
					continue;
				}

				if( zone.hasTargetUrl && !contextPtr->request.Uri().ToString().StartsWith(zone.szTargetUrl, false) )
				{
					continue;
				}

				//
				// getting here, means the request matches the current zone
				//

				break;
			}

			//
			// if the request matches a defined zone
			//

			if( zoneIndex < configPtr->zoneList.Count() )
			{
				//
				// if the listed zones are zones to exclude, then we're done
				// with this request and we should bypass the filtering engine
				//

				if( configPtr->zonesRule.flag )
				{
					return Error_T::ERR_NONE;
				}

				//
				// otherwise, the zones are to include, so lets use the zone rules file.
				//
				
				else
				{
					contextPtr->rulesPtr = configPtr->zoneList[zoneIndex]->rulesPtr;
				}
			}

			//
			// the request does not belong to a defined zone and 
			// if the listed zones are zones to exclude, then the context
			// already has the default rules, so we're good.
			//
			// otherwise, the defined zones are zones to include and we did not
			// find any, so we should bypass the filtering engine
			//

			else if( !configPtr->zonesRule.flag )
			{
				return Error_T::ERR_NONE;
			}

			//
			// Filter http headers
			//

			code = contextPtr->rulesPtr->FilterHeaders(contextPtr->request);
			if( Error_T::ERR_NONE != code )
			{
				return code;
			}

			//
			// Check if we have any entity to be filtered on the first packet read
			//

			if( contextPtr->entityLength > 0 )
			{
				code = contextPtr->rulesPtr->FilterEntitySize(contextPtr->entityLength);
				if( Error_T::ERR_NONE != code )
				{
					return code;
				}

				code = contextPtr->rulesPtr->FilterEntityData(contextPtr->rawEntity);
				if( Error_T::ERR_NONE != code )
				{
					return code;
				}
			}
		}
	}

	//
	// Filter the entity
	//
	
	else
	{
		contextPtr->entityLength += pRaw->cbInData;
		
		code = contextPtr->rulesPtr->FilterEntitySize(contextPtr->entityLength);
		if( Error_T::ERR_NONE != code )
		{
			return code;
		}

		code = contextPtr->rulesPtr->FilterEntityData(Buffer_T((unsigned char *)pRaw->pvInData, pRaw->cbInData));
		if( Error_T::ERR_NONE != code )
		{
			return code;
		}
	}

	return Error_T::ERR_NONE;
}

Error_T::Code_T OnPreProcHeaders
(
	PHTTP_FILTER_CONTEXT pFC,
	PHTTP_FILTER_PREPROC_HEADERS pHeaders,
	Config_T * configPtr
)
{
	Context_T * contextPtr = 0;
	Error_T::Code_T code = Error_T::ERR_NONE;
	char szContextAddr[11];

	// 
	// Get filter context
	//
	
	if( pFC->pFilterContext == NULL )
	{
		pFC->pFilterContext = new Context_T(pFC, configPtr->rulesPtr);
		contextPtr = (Context_T *)pFC->pFilterContext;
	}
	else
	{
		contextPtr = (Context_T *)pFC->pFilterContext;
		contextPtr->Reset(configPtr->rulesPtr);
	}

	// Now we need to get the HTTP Request data.
	//
	//  Method: GetHeader(method)
	//     Url: GetHeader(url)
	// Version: GetHeader(version)
	// Headers: GetServerVariable(ALL_RAW)

	//
	// Copy the HTTP Request data to the Context
	//

	String_T method, url, version, allRaw;

	Utils_T::GetHeader(pFC, pHeaders, "method", method);
	Utils_T::GetHeader(pFC, pHeaders, "url", url);
	Utils_T::GetHeader(pFC, pHeaders, "version", version);
	Utils_T::GetServerVariable(pFC, "ALL_RAW", allRaw);

	contextPtr->rawStream.Concat(method);
	contextPtr->rawStream.Concat(" ");
	contextPtr->rawStream.Concat(url);
	contextPtr->rawStream.Concat(" ");
	contextPtr->rawStream.Concat(version);
	contextPtr->rawStream.Concat("\r\n");
	contextPtr->rawStream.Concat(allRaw);
	contextPtr->rawStream.Concat("\r\n");

	//
	// Parse http headers
	//

	code = contextPtr->request.Parse(contextPtr->rawStream);
	if( Error_T::ERR_NONE != code )
	{
		goto SET_CONTEXT;
	}

	//
	// Go over each zone and check with which zone
	// the current request matches
	//

	unsigned int zoneIndex = 0;

	for( ; zoneIndex < configPtr->zoneList.Count() ; zoneIndex++ )
	{
		Zone_T & zone = *configPtr->zoneList[zoneIndex];

		if( zone.hasTargetAddr && !String_T(zone.szTargetAddr).Equals(contextPtr->session.localAddr, false) )
		{
			continue;
		}

		if( zone.hasTargetPort && !String_T(zone.szTargetPort).Equals(contextPtr->session.serverPort, false) )
		{
			continue;
		}

		if( zone.hasTargetUrl && !contextPtr->request.Uri().ToString().StartsWith(zone.szTargetUrl, false) )
		{
			continue;
		}

		//
		// getting here, means the request matches the current zone
		//

		break;
	}

	//
	// if the request matches a defined zone
	//

	if( zoneIndex < configPtr->zoneList.Count() )
	{
		//
		// if the listed zones are zones to exclude, then we're done
		// with this request and we should bypass the filtering engine
		//

		if( configPtr->zonesRule.flag )
		{
			goto SET_CONTEXT;
		}

		//
		// otherwise, the zones are to include, so lets use the zone rules file.
		//
		
		else
		{
			contextPtr->rulesPtr = configPtr->zoneList[zoneIndex]->rulesPtr;
		}
	}

	//
	// the request does not belong to a defined zone and 
	// if the listed zones are zones to exclude, then the context
	// already has the default rules, so we're good.
	//
	// otherwise, the defined zones are zones to include and we did not
	// find any, so we should bypass the filtering engine
	//

	else if( !configPtr->zonesRule.flag )
	{
		goto SET_CONTEXT;
	}

	//
	// Filter http headers
	//

	code = contextPtr->rulesPtr->FilterHeaders(contextPtr->request);
	if( Error_T::ERR_NONE != code )
	{
		goto SET_CONTEXT;
	}

SET_CONTEXT:

	//
	// Copy the address of the context to a string
	//
	
	_snprintf(szContextAddr, sizeof(szContextAddr) - 1, "%d", PtrToUlong(contextPtr));
	if( !pHeaders->AddHeader(pFC, "HF-CTX:", szContextAddr) )
	{
		throw Exception_T(GetLastError(), "Error %d AddHeader - %s - %d - %s", GetLastError(), __FILE__, __LINE__, __FUNCSIG__);
	}

	return code;
}

void OnSendResponse
(
	PHTTP_FILTER_CONTEXT pFC,
	PHTTP_FILTER_SEND_RESPONSE pResponse,
	Config_T * configPtr
)
{
	Context_T * contextPtr = (Context_T *)pFC->pFilterContext;

	if( contextPtr )
	{
		// If Status = 100, then the request will continue.
		contextPtr->isNewRequest = pResponse->HttpStatus != 100;

		// Lets override the default banner.
		if( !contextPtr->rulesPtr->removeBannerRule.flag )
		{
			pResponse->SetHeader(pFC, "Server:", contextPtr->rulesPtr->serverBanner.Ptr());
		}
	}

	else
	{
		// Lets override the default banner.
		if( !configPtr->rulesPtr->removeBannerRule.flag )
		{
			pResponse->SetHeader(pFC, "Server:", configPtr->rulesPtr->serverBanner.Ptr());
		}
	}
}

DWORD OnHttpFilterProc
(
	PHTTP_FILTER_CONTEXT pFC,
	DWORD dwNotifyType, 
	void * pvNotify,
	Config_T * configPtr
)
{
	Error_T::Code_T code = Error_T::ERR_WINDOWS;
	DWORD dwErr = NO_ERROR;

	//
	// get server header for all rules files
	//

	if( !configPtr->isBannerDone )
	{
		configPtr->cs.Enter();
		try
		{
			if( !configPtr->isBannerDone )
			{
				for( unsigned int ix = 0 ; ix < Rules_T::s_rulesListPtr->Count() ; ix++ )
				{
					if( Rules_T::s_rulesListPtr->operator[](ix)->serverBanner.Length() == 0 )
					{
						Utils_T::GetServerVariable(pFC, "SERVER_SOFTWARE", Rules_T::s_rulesListPtr->operator[](ix)->serverBanner);
					}
				}
				configPtr->isBannerDone = true;
			}
		}
		catch(Exception_T & exp)
		{
			configPtr->cs.Leave();
			dwErr = exp.Error;
			goto FREE_MEM;
		}
		configPtr->cs.Leave();
	}

	//
	// process notification
	//

	try
	{
		switch( dwNotifyType )
		{
			// only happends for IIS 4.x, 5.x

			case SF_NOTIFY_READ_RAW_DATA:
				code = OnReadRawData(pFC, (PHTTP_FILTER_RAW_DATA)pvNotify, configPtr);
				if( Error_T::ERR_NONE != code )
				{
					goto FREE_MEM;
				}
				break;


			// only happends for IIS 6.0

			case SF_NOTIFY_PREPROC_HEADERS:
				code = OnPreProcHeaders(pFC, (PHTTP_FILTER_PREPROC_HEADERS)pvNotify, configPtr);
				if( Error_T::ERR_NONE != code )
				{
					goto FREE_MEM;
				}
				break;

			
			// send Http response

			case SF_NOTIFY_SEND_RESPONSE:
				OnSendResponse(pFC, (PHTTP_FILTER_SEND_RESPONSE)pvNotify, configPtr);
				break;


			// clean up

			case SF_NOTIFY_END_OF_NET_SESSION:
				delete (Context_T *)pFC->pFilterContext;
				pFC->pFilterContext = NULL;
				break;
		}
	}
	catch(std::bad_alloc & )
	{
		dwErr = ERROR_NOT_ENOUGH_MEMORY;
		goto FREE_MEM;
	}
	catch(Exception_T & exp)
	{
		dwErr = exp.Error;
		goto FREE_MEM;
	}

	return SF_STATUS_REQ_NEXT_NOTIFICATION;

FREE_MEM:

	Context_T * contextPtr = (Context_T *)pFC->pFilterContext;

	if( contextPtr )
	{
		contextPtr->rulesPtr->Log(contextPtr->session, contextPtr->request, Error_T(code, dwErr));
		if( lstrlen(contextPtr->rulesPtr->szRejectFilename) > 0 )
		{
			contextPtr->Send404(pFC);
			return SF_STATUS_REQ_FINISHED_KEEP_CONN;
		}
	}

	if( Error_T::ERR_WINDOWS == code )
	{
		Trace_T::Write(dwErr, __FILE__, __LINE__, __FUNCSIG__);
		SetLastError(dwErr);
		return SF_STATUS_REQ_ERROR;
	}

	return SF_STATUS_REQ_NEXT_NOTIFICATION;
}

BOOL OnTerminateFilter
(
	DWORD // dwFlags
)
{
	if( g_configPtr )
	{
		delete g_configPtr;
		g_configPtr = 0;
	}

	Trace_T::Close();

	return TRUE;
}